package dk.kaspergsm.stormdeploy.image; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.sun.tools.attach.VirtualMachine; /** * Continuously monitors free memory on node * if freeMemory < 10% on node, do garbage collection on all JVM. * This is needed to ensure all java processes give back their unused memory when needed. * * can be executed by: * java -cp "storm-deploy-alternative.jar:/usr/lib/jvm/java-7-openjdk-amd64/lib/tools.jar" dk.kaspergsm.stormdeploy.image.MemoryMonitor * * @author Kasper Grud Skat Madsen */ class MemoryMonitor { private static Logger log = LoggerFactory.getLogger(MemoryMonitor.class); private MemoryMonitor() { } public static void main(String[] args) throws IOException { log.info("Initialized MemoryMonitor"); log.info("Software for helping Java proceses share memory"); log.info("it works by invoking garbage collection on all Java processes as needed"); while (true) { try { // Get memory information from node Long freeMem = getFreeMemoryNode(); Long totalMem = getTotalMemoryNode(); if (freeMem != null && totalMem != null) { // Calculate unused memory in % double freeMemory = ((double)freeMem / (double)totalMem) * 100; if (freeMemory < 10) { log.info("Detected system has less than 10% free memory"); GCExternalProcesses(); // invoke gc on all java processes sleep(10); } else if (freeMemory < 20) { log.info("Detected system has less than 20% free memory"); GCExternalProcesses(); // invoke gc on all java processes sleep(30); } else if (freeMemory < 40) { log.info("Detected system has less than 40% free memory"); GCExternalProcesses(); // invoke gc on all java processes sleep(60); } } sleep(5); } catch (Exception ex) { log.error("Problem", ex); } } } private static void sleep(int seconds) { try { Thread.sleep(1000*seconds); } catch (InterruptedException ie) {} } private static Long getTotalMemoryNode() { Long memTotal = 0l; try { ProcessBuilder pb = new ProcessBuilder(Arrays.asList("cat", "/proc/meminfo")); Process process = pb.start(); final InputStream is = process.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String line; while ((line = reader.readLine()) != null) { if (line.startsWith("MemTotal:")) { String noStart = line.substring(line.indexOf("MemTotal:") + 9).trim(); String noEnd = noStart.substring(0, noStart.indexOf(" ")).trim(); memTotal = Long.valueOf(noEnd) * 1024; // convert to bytes } } is.close(); process.waitFor(); } catch (Exception ex) { log.error("Problem", ex); return null; } return memTotal; } private static Long getFreeMemoryNode() { long memFree = 0, memCached = 0, memBuffer = 0; try { ProcessBuilder pb = new ProcessBuilder(Arrays.asList("cat", "/proc/meminfo")); Process process = pb.start(); final InputStream is = process.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String line; while ((line = reader.readLine()) != null) { if (line.startsWith("MemFree:")) { String noStart = line.substring(line.indexOf("MemFree:") + 8).trim(); String noEnd = noStart.substring(0, noStart.indexOf(" ")).trim(); memFree = Long.valueOf(noEnd); } if (line.startsWith("Buffers:")) { String noStart = line.substring(line.indexOf("Buffers:") + 8).trim(); String noEnd = noStart.substring(0, noStart.indexOf(" ")).trim(); memBuffer = Long.valueOf(noEnd); } if (line.startsWith("Cached:")) { String noStart = line.substring(line.indexOf("Cached:") + 7).trim(); String noEnd = noStart.substring(0, noStart.indexOf(" ")).trim(); memCached = Long.valueOf(noEnd); } } is.close(); process.waitFor(); } catch (Exception ex) { log.error("Problem", ex); return null; } return (memFree + memCached + memBuffer) * 1024; // convert to bytes } private static List<String> getAllJavaProcesses() { List<String> javaProcesses = new ArrayList<String>(); try { ProcessBuilder pb = new ProcessBuilder(Arrays.asList("jps")); Process process = pb.start(); final InputStream is = process.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String line; while ((line = reader.readLine()) != null) javaProcesses.add(line.substring(0, line.indexOf(" ")).trim()); is.close(); process.waitFor(); } catch (Exception ex) { log.error("Problem", ex); } return javaProcesses; } private static void GCExternalProcesses() { for (String jvmPid : getAllJavaProcesses()) { log.info("Requested process ( pid " + jvmPid + " ) to garbage collect"); GCExternalProcess(jvmPid); } } private static void GCExternalProcess(String pid) { System.out.println("Asked process with pid " + pid + " to do GC"); VirtualMachine vm = null; JMXConnector connector = null; try { // Attach to JVM process with pid vm = VirtualMachine.attach(pid); // Load management agent vm.loadAgent(vm.getSystemProperties().getProperty("java.home") + "/lib/management-agent.jar"); // Connect using JMX JMXServiceURL url = new JMXServiceURL(vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress")); connector = JMXConnectorFactory.newJMXConnector(url, null); connector.connect(); // Do GC ManagementFactory.newPlatformMXBeanProxy(connector.getMBeanServerConnection(), ManagementFactory.MEMORY_MXBEAN_NAME, MemoryMXBean.class).gc(); } catch (Exception ex) { log.error("Problem", ex); } finally { // Detach from JVM process try { if (connector != null) connector.close(); if (vm != null) vm.detach(); } catch (Exception ex) { log.error("Problem", ex); } } } }